<template>
    <view class="uni-file-picker">
        <view v-if="title" class="uni-file-picker__header">
            <text class="file-title">{{ title }}</text>
            <text class="file-count">{{ filesList.length }}/{{ limitLength }}</text>
        </view>
        <upload-image
            v-if="fileMediatype === 'image' && showType === 'grid'"
            :readonly="readonly"
            :image-styles="imageStyles"
            :files-list="filesList"
            :limit="limitLength"
            :disable-preview="disablePreview"
            :del-icon="delIcon"
            @upload-files="uploadFiles"
            @choose="choose"
            @del-file="delFile"
        >
            <slot>
                <view class="is-add">
                    <view class="icon-add"></view>
                    <view class="icon-add rotate"></view>
                </view>
            </slot>
        </upload-image>
        <upload-file
            v-if="fileMediatype !== 'image' || showType !== 'grid'"
            :readonly="readonly"
            :list-styles="listStyles"
            :files-list="filesList"
            :show-type="showType"
            :del-icon="delIcon"
            @upload-files="uploadFiles"
            @choose="choose"
            @del-file="delFile"
        >
            <slot><button type="primary" size="mini">选择文件</button></slot>
        </upload-file>
    </view>
</template>

<script>
import { chooseAndUploadFile, uploadCloudFiles } from "./choose-and-upload-file.js";
import { get_file_ext, get_extname, get_files_and_is_max, get_file_info, get_file_data } from "./utils.js";
import uploadImage from "./upload-image.vue";
import uploadFile from "./upload-file.vue";
let fileInput = null;
/**
 * FilePicker 文件选择上传
 * @description 文件选择上传组件，可以选择图片、视频等任意文件并上传到当前绑定的服务空间
 * @tutorial https://ext.dcloud.net.cn/plugin?id=4079
 * @property {Object|Array}	value	组件数据，通常用来回显 ,类型由return-type属性决定
 * @property {Boolean}	disabled = [true|false]	组件禁用
 * 	@value true 	禁用
 * 	@value false 	取消禁用
 * @property {Boolean}	readonly = [true|false]	组件只读，不可选择，不显示进度，不显示删除按钮
 * 	@value true 	只读
 * 	@value false 	取消只读
 * @property {String}	return-type = [array|object]	限制 value 格式，当为 object 时 ，组件只能单选，且会覆盖
 * 	@value array	规定 value 属性的类型为数组
 * 	@value object	规定 value 属性的类型为对象
 * @property {Boolean}	disable-preview = [true|false]	禁用图片预览，仅 mode:grid 时生效
 * 	@value true 	禁用图片预览
 * 	@value false 	取消禁用图片预览
 * @property {Boolean}	del-icon = [true|false]	是否显示删除按钮
 * 	@value true 	显示删除按钮
 * 	@value false 	不显示删除按钮
 * @property {Boolean}	auto-upload = [true|false]	是否自动上传，值为true则只触发@select,可自行上传
 * 	@value true 	自动上传
 * 	@value false 	取消自动上传
 * @property {Number|String}	limit	最大选择个数 ，h5 会自动忽略多选的部分
 * @property {String}	title	组件标题，右侧显示上传计数
 * @property {String}	mode = [list|grid]	选择文件后的文件列表样式
 * 	@value list 	列表显示
 * 	@value grid 	宫格显示
 * @property {String}	file-mediatype = [image|video|all]	选择文件类型
 * 	@value image	只选择图片
 * 	@value video	只选择视频
 * 	@value all		选择所有文件
 * @property {Array}	file-extname	选择文件后缀，根据 file-mediatype 属性而不同
 * @property {Object}	list-style	mode:list 时的样式
 * @property {Object}	image-styles	选择文件后缀，根据 file-mediatype 属性而不同
 * @event {Function} select 	选择文件后触发
 * @event {Function} progress 文件上传时触发
 * @event {Function} success 	上传成功触发
 * @event {Function} fail 		上传失败触发
 * @event {Function} delete 	文件从列表移除时触发
 */
export default {
    name: "UniFilePicker",
    components: {
        uploadImage,
        uploadFile
    },
    options: {
        virtualHost: true
    },
    props: {
        modelValue: {
            type: [Array, Object],
            default() {
                return [];
            }
        },
        value: {
            type: [Array, Object],
            default() {
                return [];
            }
        },
        disabled: {
            type: Boolean,
            default: false
        },
        disablePreview: {
            type: Boolean,
            default: false
        },
        delIcon: {
            type: Boolean,
            default: true
        },
        // 自动上传
        autoUpload: {
            type: Boolean,
            default: true
        },
        // 最大选择个数 ，h5只能限制单选或是多选
        limit: {
            type: [Number, String],
            default: 9
        },
        // 列表样式 grid | list | list-card
        mode: {
            type: String,
            default: "grid"
        },
        // 选择文件类型  image/video/all
        fileMediatype: {
            type: String,
            default: "image"
        },
        // 文件类型筛选
        fileExtname: {
            type: [Array, String],
            default() {
                return [];
            }
        },
        title: {
            type: String,
            default: ""
        },
        listStyles: {
            type: Object,
            default() {
                return {
                    // 是否显示边框
                    border: true,
                    // 是否显示分隔线
                    dividline: true,
                    // 线条样式
                    borderStyle: {}
                };
            }
        },
        imageStyles: {
            type: Object,
            default() {
                return {
                    width: "auto",
                    height: "auto"
                };
            }
        },
        readonly: {
            type: Boolean,
            default: false
        },
        returnType: {
            type: String,
            default: "array"
        },
        sizeType: {
            type: Array,
            default() {
                return ["original", "compressed"];
            }
        },
        sourceType: {
            type: Array,
            default() {
                return ["album", "camera"];
            }
        },
        provider: {
            type: String,
            default: "" // 默认上传到 unicloud 内置存储 extStorage 扩展存储
        }
    },
    emits: ["select", "success", "fail", "progress", "delete", "update:modelValue", "input"],
    data() {
        return {
            files: [],
            localValue: []
        };
    },
    computed: {
        filesList() {
            let files = [];
            this.files.forEach((v) => {
                files.push(v);
            });
            return files;
        },
        showType() {
            if (this.fileMediatype === "image") {
                return this.mode;
            }
            return "list";
        },
        limitLength() {
            if (this.returnType === "object") {
                return 1;
            }
            if (!this.limit) {
                return 1;
            }
            if (this.limit >= 9) {
                return 9;
            }
            return this.limit;
        }
    },
    watch: {
        value: {
            handler(newVal, oldVal) {
                this.setValue(newVal, oldVal);
            },
            immediate: true
        },
        modelValue: {
            handler(newVal, oldVal) {
                this.setValue(newVal, oldVal);
            },
            immediate: true
        }
    },
    created() {
        // TODO 兼容不开通服务空间的情况
        if (!(uniCloud.config && uniCloud.config.provider)) {
            this.noSpace = true;
            uniCloud.chooseAndUploadFile = chooseAndUploadFile;
        }
        this.form = this.getForm("uniForms");
        this.formItem = this.getForm("uniFormsItem");
        if (this.form && this.formItem) {
            if (this.formItem.name) {
                this.rename = this.formItem.name;
                this.form.inputChildrens.push(this);
            }
        }
    },
    methods: {
        /**
         * 公开用户使用，清空文件
         * @param {Object} index
         */
        clearFiles(index) {
            if (index !== 0 && !index) {
                this.files = [];
                this.$nextTick(() => {
                    this.setEmit();
                });
            } else {
                this.files.splice(index, 1);
            }
            this.$nextTick(() => {
                this.setEmit();
            });
        },
        /**
         * 公开用户使用，继续上传
         */
        upload() {
            let files = [];
            this.files.forEach((v, index) => {
                if (v.status === "ready" || v.status === "error") {
                    files.push(Object.assign({}, v));
                }
            });
            return this.uploadFiles(files);
        },
        async setValue(newVal, oldVal) {
            const newData = async (v) => {
                const reg = /cloud:\/\/([\w.]+\/?)\S*/;
                let url = "";
                if (v.fileID) {
                    url = v.fileID;
                } else {
                    url = v.url;
                }
                if (reg.test(url)) {
                    v.fileID = url;
                    v.url = await this.getTempFileURL(url);
                }
                if (v.url) v.path = v.url;
                return v;
            };
            if (this.returnType === "object") {
                if (newVal) {
                    await newData(newVal);
                } else {
                    newVal = {};
                }
            } else {
                if (!newVal) newVal = [];
                for (let i = 0; i < newVal.length; i++) {
                    let v = newVal[i];
                    await newData(v);
                }
            }
            this.localValue = newVal;
            if (this.form && this.formItem && !this.is_reset) {
                this.is_reset = false;
                this.formItem.setValue(this.localValue);
            }
            let filesData = Object.keys(newVal).length > 0 ? newVal : [];
            this.files = [].concat(filesData);
        },

        /**
         * 选择文件
         */
        choose() {
            if (this.disabled) return;
            if (
                this.files.length >= Number(this.limitLength) &&
                this.showType !== "grid" &&
                this.returnType === "array"
            ) {
                uni.showToast({
                    title: `您最多选择 ${this.limitLength} 个文件`,
                    icon: "none"
                });
                return;
            }
            this.chooseFiles();
        },

        /**
         * 选择文件并上传
         */
        chooseFiles() {
            const _extname = get_extname(this.fileExtname);
            // 获取后缀
            uniCloud
                .chooseAndUploadFile({
                    type: this.fileMediatype,
                    compressed: false,
                    sizeType: this.sizeType,
                    sourceType: this.sourceType,
                    // TODO 如果为空，video 有问题
                    extension: _extname.length > 0 ? _extname : undefined,
                    count: this.limitLength - this.files.length, //默认9
                    onChooseFile: this.chooseFileCallback,
                    onUploadProgress: (progressEvent) => {
                        this.setProgress(progressEvent, progressEvent.index);
                    }
                })
                .then((result) => {
                    this.setSuccessAndError(result.tempFiles);
                })
                .catch((err) => {
                    console.log("选择失败", err);
                });
        },

        /**
         * 选择文件回调
         * @param {Object} res
         */
        async chooseFileCallback(res) {
            const _extname = get_extname(this.fileExtname);
            const is_one =
                (Number(this.limitLength) === 1 && this.disablePreview && !this.disabled) ||
                this.returnType === "object";
            // 如果这有一个文件 ，需要清空本地缓存数据
            if (is_one) {
                this.files = [];
            }

            let { filePaths, files } = get_files_and_is_max(res, _extname);
            if (!(_extname && _extname.length > 0)) {
                filePaths = res.tempFilePaths;
                files = res.tempFiles;
            }

            let currentData = [];
            for (let i = 0; i < files.length; i++) {
                if (this.limitLength - this.files.length <= 0) break;
                files[i].uuid = Date.now();
                let filedata = await get_file_data(files[i], this.fileMediatype);
                filedata.progress = 0;
                filedata.status = "ready";
                this.files.push(filedata);
                currentData.push({
                    ...filedata,
                    file: files[i]
                });
            }
            this.$emit("select", {
                tempFiles: currentData,
                tempFilePaths: filePaths
            });
            res.tempFiles = files;
            // 停止自动上传
            if (!this.autoUpload || this.noSpace) {
                res.tempFiles = [];
            }
            res.tempFiles.forEach((fileItem, index) => {
                this.provider && (fileItem.provider = this.provider);
                const fileNameSplit = fileItem.name.split(".");
                const ext = fileNameSplit.pop();
                const fileName = fileNameSplit.join(".").replace(/[\s\/\?<>\\:\*\|":]/g, "_");
                fileItem.cloudPath = fileName + "_" + Date.now() + "_" + index + "." + ext;
            });
        },

        /**
         * 批传
         * @param {Object} e
         */
        uploadFiles(files) {
            files = [].concat(files);
            return uploadCloudFiles
                .call(this, files, 5, (res) => {
                    this.setProgress(res, res.index, true);
                })
                .then((result) => {
                    this.setSuccessAndError(result);
                    return result;
                })
                .catch((err) => {
                    console.log(err);
                });
        },

        /**
         * 成功或失败
         */
        async setSuccessAndError(res, fn) {
            let successData = [];
            let errorData = [];
            let tempFilePath = [];
            let errorTempFilePath = [];
            for (let i = 0; i < res.length; i++) {
                const item = res[i];
                const index = item.uuid ? this.files.findIndex((p) => p.uuid === item.uuid) : item.index;

                if (index === -1 || !this.files) break;
                if (item.errMsg === "request:fail") {
                    this.files[index].url = item.path;
                    this.files[index].status = "error";
                    this.files[index].errMsg = item.errMsg;
                    // this.files[index].progress = -1
                    errorData.push(this.files[index]);
                    errorTempFilePath.push(this.files[index].url);
                } else {
                    this.files[index].errMsg = "";
                    this.files[index].fileID = item.url;
                    const reg = /cloud:\/\/([\w.]+\/?)\S*/;
                    if (reg.test(item.url)) {
                        this.files[index].url = await this.getTempFileURL(item.url);
                    } else {
                        this.files[index].url = item.url;
                    }

                    this.files[index].status = "success";
                    this.files[index].progress += 1;
                    successData.push(this.files[index]);
                    tempFilePath.push(this.files[index].fileID);
                }
            }

            if (successData.length > 0) {
                this.setEmit();
                // 状态改变返回
                this.$emit("success", {
                    tempFiles: this.backObject(successData),
                    tempFilePaths: tempFilePath
                });
            }

            if (errorData.length > 0) {
                this.$emit("fail", {
                    tempFiles: this.backObject(errorData),
                    tempFilePaths: errorTempFilePath
                });
            }
        },

        /**
         * 获取进度
         * @param {Object} progressEvent
         * @param {Object} index
         * @param {Object} type
         */
        setProgress(progressEvent, index, type) {
            const fileLenth = this.files.length;
            const percentNum = (index / fileLenth) * 100;
            const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
            let idx = index;
            if (!type) {
                idx = this.files.findIndex((p) => p.uuid === progressEvent.tempFile.uuid);
            }
            if (idx === -1 || !this.files[idx]) return;
            // fix by mehaotian 100 就会消失，-1 是为了让进度条消失
            this.files[idx].progress = percentCompleted - 1;
            // 上传中
            this.$emit("progress", {
                index: idx,
                progress: parseInt(percentCompleted),
                tempFile: this.files[idx]
            });
        },

        /**
         * 删除文件
         * @param {Object} index
         */
        delFile(index) {
            this.$emit("delete", {
                index,
                tempFile: this.files[index],
                tempFilePath: this.files[index].url
            });
            this.files.splice(index, 1);
            this.$nextTick(() => {
                this.setEmit();
            });
        },

        /**
         * 获取文件名和后缀
         * @param {Object} name
         */
        getFileExt(name) {
            const last_len = name.lastIndexOf(".");
            const len = name.length;
            return {
                name: name.substring(0, last_len),
                ext: name.substring(last_len + 1, len)
            };
        },

        /**
         * 处理返回事件
         */
        setEmit() {
            let data = [];
            if (this.returnType === "object") {
                data = this.backObject(this.files)[0];
                this.localValue = data ? data : null;
            } else {
                data = this.backObject(this.files);
                if (!this.localValue) {
                    this.localValue = [];
                }
                this.localValue = [...data];
            }
            // #ifdef VUE3
            this.$emit("update:modelValue", this.localValue);
            // #endif
            // #ifndef VUE3
            this.$emit("input", this.localValue);
            // #endif
        },

        /**
         * 处理返回参数
         * @param {Object} files
         */
        backObject(files) {
            let newFilesData = [];
            files.forEach((v) => {
                newFilesData.push({
                    extname: v.extname,
                    fileType: v.fileType,
                    image: v.image,
                    name: v.name,
                    path: v.path,
                    size: v.size,
                    fileID: v.fileID,
                    url: v.url,
                    // 修改删除一个文件后不能再上传的bug, #694
                    uuid: v.uuid,
                    status: v.status,
                    cloudPath: v.cloudPath
                });
            });
            return newFilesData;
        },
        async getTempFileURL(fileList) {
            fileList = {
                fileList: [].concat(fileList)
            };
            const urls = await uniCloud.getTempFileURL(fileList);
            return urls.fileList[0].tempFileURL || "";
        },
        /**
         * 获取父元素实例
         */
        getForm(name = "uniForms") {
            let parent = this.$parent;
            let parentName = parent.$options.name;
            while (parentName !== name) {
                parent = parent.$parent;
                if (!parent) return false;
                parentName = parent.$options.name;
            }
            return parent;
        }
    }
};
</script>

<style>
.uni-file-picker {
    /* #ifndef APP-NVUE */
    box-sizing: border-box;
    overflow: hidden;
    width: 100%;
    /* #endif */
    flex: 1;
}

.uni-file-picker__header {
    padding-top: 5px;
    padding-bottom: 10px;
    /* #ifndef APP-NVUE */
    display: flex;
    /* #endif */
    justify-content: space-between;
}

.file-title {
    font-size: 14px;
    color: #333;
}

.file-count {
    font-size: 14px;
    color: #999;
}

.is-add {
    /* #ifndef APP-NVUE */
    display: flex;
    /* #endif */
    align-items: center;
    justify-content: center;
}

.icon-add {
    width: 50px;
    height: 5px;
    background-color: #f1f1f1;
    border-radius: 2px;
}

.rotate {
    position: absolute;
    transform: rotate(90deg);
}
</style>
